using System;
using System.Collections.Generic;
using System.Text;
using System.Drawing;
using System.Runtime.InteropServices;
using System.Diagnostics;
using GoodStuff;

namespace SampleProcessors
{
    public class ImageConvolver
    {
        Int32[] _kernel;
        Int32[] _sumKernel;
        public Int32[] Kernel
        {
            get { return _kernel; }
            set 
            { 
                _kernel = value;
                _sumKernel = new Int32[_kernel.Length];
                _sumKernel[0] = _kernel[0];
                for( int ii = 1; ii < _kernel.Length; ++ii)
                    _sumKernel[ii] = _sumKernel[ii - 1] + _kernel[ii];
            }
        }

        public ImageConvolver()
        {
        }

        public void SetKernel(float[] floatKernel)
        {
            float sum = 0;
            for (int i = 0; i < floatKernel.Length; ++i)
            {
                sum += floatKernel[i];
            }
            float scalefactor = 0x100 / sum;
            _kernel = new Int32[floatKernel.Length];
            for (int i = 0; i < floatKernel.Length; ++i)
            {
                _kernel[i] = Convert.ToInt32(floatKernel[i] * scalefactor);
            }

        }

        public Byte[,] Convolve(Byte[,] inputArray)
        {
            int width = inputArray.GetLength(0);
            int height = inputArray.GetLength(1);
            Int32[,] temp1 = new Int32[width, height];
            int[] accumulator = new int[_kernel.Length];
            GCHandle inputGch = GCHandle.Alloc(inputArray, GCHandleType.Pinned);
            GCHandle temp1Gch = GCHandle.Alloc(temp1, GCHandleType.Pinned);
            GCHandle accumulatorGch = GCHandle.Alloc(accumulator, GCHandleType.Pinned);
            int halfKernelLength = _kernel.Length / 2;
            int midKernelLength = halfKernelLength + 1;
            int kernelLength = _kernel.Length;
            unsafe
            {
                Byte* inPixel = (Byte*)inputGch.AddrOfPinnedObject();
                Int32* outPixel = (Int32*)temp1Gch.AddrOfPinnedObject();
                int accumulatorIndex = 0;
                int ii, jj, kk;


                for (kk = 0; kk < height; ++kk)
                {
                    for (jj = 0; jj < midKernelLength; ++jj)
                    {
                        accumulator[jj + 1] += _sumKernel[halfKernelLength - jj] * *inPixel;
                    }
                    ++inPixel;
                    for (jj = 1; jj < halfKernelLength; ++jj)
                    {
                        for (ii = 0; ii < midKernelLength + jj; ++ii)
                        {
                            accumulator[ii + 1] += _kernel[halfKernelLength + jj - ii] * *inPixel;
                        }
                        ++inPixel;
                    }
                    for (; jj < width - halfKernelLength; ++jj)
                    {
                        for (ii = 0; ii < kernelLength - 1; ++ii)
                        {
                            accumulator[ii] = accumulator[ii + 1] + _kernel[ii] * *inPixel;
                        }
                        accumulator[ii] = _kernel[ii] * *(inPixel++);
                        *(outPixel++) = accumulator[0];
                        if ((inPixel > (byte*)inputGch.AddrOfPinnedObject() + width * (kk+1)) || (outPixel > (Int32*)temp1Gch.AddrOfPinnedObject() + width * (kk+1)))
                            throw new Exception("pointer out of range");
                    }
                    for (jj = 0; jj < midKernelLength; ++jj)
                    {
                        *(outPixel++) = accumulator[jj + 1] + _sumKernel[jj] * *inPixel;
                    }
                    if ((inPixel > (byte*)inputGch.AddrOfPinnedObject() + width * (kk+1)) || (outPixel > (Int32*)temp1Gch.AddrOfPinnedObject() + width * (kk+1)))
                        throw new Exception("pointer out of range");
                    for (jj = 0; jj < kernelLength; ++jj)
                        accumulator[jj] = 0;
                    ++inPixel;
                }
            }
            return null;
        }
    }
}
